The following object is masked from 'package:ggplot2':
last_plot
The following object is masked from 'package:stats':
filter
The following object is masked from 'package:graphics':
layout
library(forcats)# reading in the events datasetevents <-read.csv("../data/security_incidents.csv")# ignoring the year 2025events <- events %>%filter(Year !=2025)# loading in my themesource("../assets/my_theme.R")
Checking For Potential Issues
head(events)
Incident.ID Year Month Day Country.Code Country Region District
1 1 1997 1 NA KH Cambodia Banteay Meanchey
2 2 1997 1 NA RW Rwanda Northern Musanze
3 3 1997 2 NA TJ Tajikistan
4 4 1997 2 NA SO Somalia Lower Juba Kismayo
5 5 1997 2 14 RW Rwanda Kigali Kigali
6 7 1997 5 NA CD DR Congo
City UN INGO ICRC NRCS.and.IFRC NNGO Other Nationals.killed
1 0 0 1 0 0 0 1
2 Ruhengeri 0 4 0 0 0 0 0
3 4 0 2 0 0 0 0
4 Kismayo 0 1 0 0 0 0 0
5 Kigali 1 0 0 0 0 0 1
6 0 0 0 10 0 0 10
Nationals.wounded Nationals.kidnapped Total.nationals Internationals.killed
1 0 0 1 0
2 0 0 0 3
3 0 4 4 0
4 0 0 0 1
5 0 0 1 0
6 0 0 10 0
Internationals.wounded Internationals.kidnapped Total.internationals
1 0 0 0
2 1 0 4
3 0 2 2
4 0 0 1
5 0 0 0
6 0 0 0
Total.killed Total.wounded Total.kidnapped Total.affected Gender.Male
1 1 0 0 1 0
2 3 1 0 4 3
3 0 0 6 6 0
4 1 0 0 1 0
5 1 0 0 1 0
6 10 0 0 10 0
Gender.Female Gender.Unknown Means.of.attack Attack.context
1 0 1 Unknown Unknown
2 1 0 Shooting Raid
3 0 6 Kidnapping Unknown
4 0 1 Unknown Unknown
5 0 1 Shooting Individual attack
6 0 10 Unknown Combat/Crossfire
Location Latitude Longitude Motive
1 Unknown 14.070929 103.09992 Unknown
2 Office/compound -1.499840 29.63497 Unknown
3 Unknown 38.628173 70.81565
4 Unknown -0.358216 42.54509 Political
5 Unknown -1.950851 30.06151 Political
6 Unknown -2.981434 23.82226 Incidental
Actor.type
1 Unknown
2 Unknown
3 Unknown
4 Non-state armed group: Regional
5 Unknown
6 Non-state armed group: National
Actor.name
1 Unknown
2 Unknown
3 Unknown
4 Al-Itihaad al-Islamiya
5 Unknown
6 Alliance of Democratic Forces for the Liberation of Congo-Zaire (ADFL)
Details
1 1 ICRC national staff killed while working in Banteay Meanchey province.
2 3 INGO international (Spanish) staff killed, 1 INGO international (US) staff gravely wounded during armed raid on INGO compound in Ruhengeri, Rwanda.
3 3 UN national staff, 1 UN international (Nigerian) staff, 1 ICRC international staff and 1 ICRC national staff kidnapped along with other (not included) UN staff, journalist and Tajik govt rep; released 48 hours later.
4 1 INGO international staff killed by Al ittihad militia in Kismayo.
5 1 UN national staff shot and killed in Kigali Feb 14.
6 10 NRCS staff first aid workers killed in fighting between Zairean troops and rebels from the Alliance of Democratic Forces for the Liberation of Congo-Zaire (ADFL).
Verified Source
1 Archived Archived
2 Archived Archived
3 Archived Archived
4 Archived Archived
5 Archived Archived
6 Archived Archived
summary(events)
Incident.ID Year Month Day
Min. : 1 Min. :1997 Min. : 1.000 Min. : 1.00
1st Qu.:1113 1st Qu.:2010 1st Qu.: 4.000 1st Qu.: 8.00
Median :2230 Median :2016 Median : 7.000 Median :15.00
Mean :2231 Mean :2015 Mean : 6.592 Mean :15.58
3rd Qu.:3352 3rd Qu.:2021 3rd Qu.:10.000 3rd Qu.:23.00
Max. :4501 Max. :2024 Max. :12.000 Max. :31.00
NA's :48 NA's :377
Country.Code Country Region District
Length:4290 Length:4290 Length:4290 Length:4290
Class :character Class :character Class :character Class :character
Mode :character Mode :character Mode :character Mode :character
City UN INGO ICRC
Length:4290 Min. : 0.0000 Min. : 0.0000 Min. :0.00000
Class :character 1st Qu.: 0.0000 1st Qu.: 0.0000 1st Qu.:0.00000
Mode :character Median : 0.0000 Median : 0.0000 Median :0.00000
Mean : 0.4103 Mean : 0.8142 Mean :0.05162
3rd Qu.: 0.0000 3rd Qu.: 1.0000 3rd Qu.:0.00000
Max. :92.0000 Max. :49.0000 Max. :8.00000
NA's :9
NRCS.and.IFRC NNGO Other Nationals.killed
Min. : 0.000 Min. : 0.0000 Min. :0.00000 Min. : 0.0000
1st Qu.: 0.000 1st Qu.: 0.0000 1st Qu.:0.00000 1st Qu.: 0.0000
Median : 0.000 Median : 0.0000 Median :0.00000 Median : 0.0000
Mean : 0.121 Mean : 0.4723 Mean :0.02448 Mean : 0.6466
3rd Qu.: 0.000 3rd Qu.: 0.0000 3rd Qu.:0.00000 3rd Qu.: 1.0000
Max. :19.000 Max. :15.0000 Max. :5.00000 Max. :70.0000
NA's :9 NA's :9
Nationals.wounded Nationals.kidnapped Total.nationals Internationals.killed
Min. : 0.0000 Min. : 0.0000 Min. : 0.000 Min. : 0.00000
1st Qu.: 0.0000 1st Qu.: 0.0000 1st Qu.: 1.000 1st Qu.: 0.00000
Median : 0.0000 Median : 0.0000 Median : 1.000 Median : 0.00000
Mean : 0.6289 Mean : 0.4114 Mean : 1.687 Mean : 0.05618
3rd Qu.: 1.0000 3rd Qu.: 0.0000 3rd Qu.: 2.000 3rd Qu.: 0.00000
Max. :37.0000 Max. :19.0000 Max. :92.000 Max. :11.00000
Internationals.wounded Internationals.kidnapped Total.internationals
Min. : 0.00000 Min. : 0.00000 Min. : 0.0000
1st Qu.: 0.00000 1st Qu.: 0.00000 1st Qu.: 0.0000
Median : 0.00000 Median : 0.00000 Median : 0.0000
Mean : 0.06457 Mean : 0.08461 Mean : 0.2054
3rd Qu.: 0.00000 3rd Qu.: 0.00000 3rd Qu.: 0.0000
Max. :15.00000 Max. :12.00000 Max. :15.0000
Total.killed Total.wounded Total.kidnapped Total.affected
Min. : 0.0000 Min. : 0.0000 Min. : 0.000 Min. : 0.000
1st Qu.: 0.0000 1st Qu.: 0.0000 1st Qu.: 0.000 1st Qu.: 1.000
Median : 0.0000 Median : 0.0000 Median : 0.000 Median : 1.000
Mean : 0.7028 Mean : 0.6935 Mean : 0.496 Mean : 1.892
3rd Qu.: 1.0000 3rd Qu.: 1.0000 3rd Qu.: 0.000 3rd Qu.: 2.000
Max. :70.0000 Max. :37.0000 Max. :20.000 Max. :92.000
Gender.Male Gender.Female Gender.Unknown Means.of.attack
Min. : 0.0000 Min. :0.0000 Min. : 0.0000 Length:4290
1st Qu.: 0.0000 1st Qu.:0.0000 1st Qu.: 0.0000 Class :character
Median : 1.0000 Median :0.0000 Median : 0.0000 Mode :character
Mean : 0.8963 Mean :0.1394 Mean : 0.8564
3rd Qu.: 1.0000 3rd Qu.:0.0000 3rd Qu.: 1.0000
Max. :17.0000 Max. :7.0000 Max. :92.0000
Attack.context Location Latitude Longitude
Length:4290 Length:4290 Min. :-34.884 Min. :-102.28
Class :character Class :character 1st Qu.: 5.836 1st Qu.: 28.75
Mode :character Mode :character Median : 13.426 Median : 34.47
Mean : 16.732 Mean : 36.53
3rd Qu.: 33.096 3rd Qu.: 45.40
Max. : 52.253 Max. : 179.01
NA's :13 NA's :13
Motive Actor.type Actor.name Details
Length:4290 Length:4290 Length:4290 Length:4290
Class :character Class :character Class :character Class :character
Mode :character Mode :character Mode :character Mode :character
Verified Source
Length:4290 Length:4290
Class :character Class :character
Mode :character Mode :character
ggplot(events %>%filter(!is.na(Motive) & Motive !=""), aes(x = Motive)) +# ignore NA/emptygeom_bar(fill ="#0D0887", color ="#0D0887") +# custom colortheme_minimal() +labs(title ="Number of Incidents by Motive Type",x ="Motive",y ="Count" ) + my_theme
Animated Time Series
# dropping 'OTHER' AND 'DISPUTED':events <- events %>%filter(!Motive %in%c("Other", "Disputed", "Unknown"))# group by the year and the motive typeevents_summary <- events %>%filter(!is.na(Motive) & Motive !="") %>%group_by(Year, Motive) %>%summarise(n =n(), .groups ='drop')# creating a time series plot with each line being a motive typetime <-ggplot(events_summary, aes(x = Year, y = n, group = Motive, color = Motive)) +geom_line() +geom_point() +scale_color_viridis(discrete =TRUE) +ggtitle("Motives of Incidences over the Years") +theme_ipsum() +ylab("Number of Motive Types") +transition_reveal(Year)# animating and savinganimate(time)
`geom_line()`: Each group consists of only one observation.
ℹ Do you need to adjust the group aesthetic?
`geom_line()`: Each group consists of only one observation.
ℹ Do you need to adjust the group aesthetic?
`geom_line()`: Each group consists of only one observation.
ℹ Do you need to adjust the group aesthetic?
`geom_line()`: Each group consists of only one observation.
ℹ Do you need to adjust the group aesthetic?
Static Time Series
# aggregating by motive typeevents_summary <- events %>%filter(!is.na(Motive) & Motive !="") %>%group_by(Year, Motive) %>%summarise(n =n(), .groups ='drop')# taking only three motivesfiltered_summary <- events_summary %>%filter(Motive %in%c("Economic", "Political", "Incidental"))# Static time series plotggplot(filtered_summary, aes(x = Year, y = n, color = Motive)) +geom_line() +geom_point() +scale_color_manual(values =c("Political"="#F0F921","Economic"="#F89441","Incidental"="#933b94" )) +ggtitle("") +ylab("") +xlab("") + my_theme +theme(legend.position ="none")
Political Choropleth
# subsetting by just the political motive and by countrypolitical_events <- events %>%filter(Motive =="Political") %>%group_by(Country) %>%summarise(count =n())# choropleth of political events across the countryplot_geo(political_events) %>%# making it interactiveadd_trace(locations =~Country,locationmode ='country names',z =~count,colorscale =list( # custom color scalec(0, "#0D0887"), c(0.33, "#933b94"),c(0.66, "#F89441"),c(1, "#F0F921") ),text =~paste(Country, "<br>Count:", count),hoverinfo ="text" ) %>%colorbar(title =list(text ="Count", font =list(family ="Didot"))) %>%layout(title =list(text ="Count of Politically Motivated Incidents by Country",font =list(family ="Didot") ),font =list(family ="Didot"),geo =list(showframe =FALSE) )
Economic Choropleth
# subsetting by just the economic motive and by countryeconomic_events <- events %>%filter(Motive =="Economic") %>%group_by(Country) %>%summarise(count =n())# choropleth of economic events across the countryplot_geo(economic_events) %>%# making it interactiveadd_trace(locations =~Country,locationmode ='country names',z =~count,colorscale =list( # custom color scalec(0, "#0D0887"), c(0.33, "#933b94"),c(0.66, "#F89441"),c(1, "#F0F921") ),text =~paste(Country, "<br>Count:", count),hoverinfo ="text" ) %>%colorbar(title =list(text ="Count", font =list(family ="Didot"))) %>%layout(title =list(text ="Count of Economically Motivated Incidents by Country",font =list(family ="Didot") ),font =list(family ="Didot"),geo =list(showframe =FALSE) )
Incidental Choropleth
# subsetting by just the incidental motive and by countryinc_events <- events %>%filter(Motive =="Incidental") %>%group_by(Country) %>%summarise(count =n())# choropleth of incidental events across the countryplot_geo(inc_events) %>%# making it interactiveadd_trace(locations =~Country,locationmode ='country names',z =~count,colorscale =list( # custom color scalec(0, "#0D0887"), c(0.33, "#933b94"),c(0.66, "#F89441"),c(1, "#F0F921") ),text =~paste(Country, "<br>Count:", count),hoverinfo ="text" ) %>%colorbar(title =list(text ="Count", font =list(family ="Didot"))) %>%layout(title =list(text ="Count of Incidentally Motivated Incidents by Country",font =list(family ="Didot") ),font =list(family ="Didot"),geo =list(showframe =FALSE) )
Dropping ‘Unknown’
# DROPPING THE 'UNKNOWN' FOR THE RESTevents <- events %>%filter(!Motive %in%c("Unknown"))
Stacked Bar Plot
# aggregating the harm types by the motive typeharm_summary <- events %>%filter(!is.na(Motive) & Motive !=""&!Motive %in%c("Disputed", "Other")) %>%group_by(Motive) %>%summarise(Total_Killed =sum(`Total.killed`, na.rm =TRUE),Total_Wounded =sum(`Total.wounded`, na.rm =TRUE),Total_Kidnapped =sum(`Total.kidnapped`, na.rm =TRUE)) %>%pivot_longer(cols =starts_with("Total"), names_to ="Metric", values_to ="Count") # reshaping to long format# ordering motive types by total count (highest count = first)harm_summary <- harm_summary %>%mutate(Motive_Type =fct_reorder(Motive, -Count))# stacked bar plotp <-ggplot(harm_summary, aes(x = Motive_Type, y = Count, fill = Metric)) +geom_bar(stat ="identity") +coord_flip() +# making it horizontalscale_fill_manual(values =c( # custom colors"Total_Killed"="#F89441","Total_Wounded"="#933b94","Total_Kidnapped"="#0D0887" )) +labs(title ="Total Wounded, Kidnapped, and Affected by Motive Type",x ="Motive", y =NULL, fill ="Metric") + my_themeggplotly(p) # making it interactive
Pie Chart by Agency
# sums events by the agency and the motiveagency_props <- events %>%filter(!is.na(Motive) & Motive !="") %>%# drop NA/missingpivot_longer(cols =c("UN", "INGO", "ICRC", "NRCS.and.IFRC", "NNGO", "Other"),names_to ="Agency", values_to ="IncidentCount") %>%# long formatfilter(!is.na(IncidentCount) & IncidentCount >0) %>%# Only include non-zero incidentsgroup_by(Agency, Motive) %>%summarise(Count =sum(IncidentCount, na.rm =TRUE), .groups ="drop") %>%group_by(Agency) %>%mutate(Percent = Count /sum(Count)) %>%# calculate percent shareungroup()# creating pie charts for motives across agenciesggplot(agency_props, aes(x =1, y = Percent, fill = Motive)) +geom_col(width =0.6, color ="white") +coord_polar(theta ="y") +facet_wrap(~ Agency) +# facet by the agency typescale_fill_manual(values =c( # custom colors"Political"="#F0F921","Economic"="#F89441","Incidental"="#933b94" )) +theme_void(base_family ="Didot", base_size =14) +theme( # manually appplying theme due to pie chart issuesaxis.text =element_blank(),axis.ticks =element_blank(),axis.title =element_blank(),panel.grid =element_blank(),panel.background =element_blank(),strip.text =element_text(family ="Didot", size =14),strip.background =element_blank(),plot.title =element_text(family ="Didot", size =16, face ="bold"),legend.title =element_text(family ="Didot"),legend.text =element_text(family ="Didot", size =12) ) +labs(title ="Distribution of Incidence Motive Type per Agency",fill ="Motive")
Reverse Pie Chart
# Pivot agency columns longagency_props <- events %>%filter(!is.na(Motive) & Motive !="") %>%pivot_longer(cols =c("UN", "INGO", "ICRC", "NRCS.and.IFRC", "NNGO", "Other"),names_to ="Agency", values_to ="IncidentCount") %>%filter(!is.na(IncidentCount) & IncidentCount >0) %>%# Only include non-zero incidentsgroup_by(Agency, Motive) %>%summarise(Count =sum(IncidentCount, na.rm =TRUE), .groups ="drop") %>%group_by(Agency) %>%mutate(Percent = Count /sum(Count)) %>%ungroup()ggplot(agency_props, aes(x =1, y = Percent, fill = Motive)) +geom_col(width =0.6, color ="white") +coord_polar(theta ="y") +facet_wrap(~ Agency) +scale_fill_viridis_d(option ="plasma") +theme_void() +labs(title ="Distribution of Motives per Agency",fill ="Motive")
Grouped Bar Chart (Nats versus Inters)
# sums the number of events by national/internationalnationality_long <- events %>%filter(!is.na(Motive) & Motive !="") %>%# ignore missingpivot_longer(cols =c(Total.nationals, Total.internationals),names_to ="Group", values_to ="Count") %>%# pivot longmutate(Group =ifelse(Group =="Total.nationals", "Nationals", "Internationals"))# aggregate totals by motive and groupnationality_summary <- nationality_long %>%group_by(Motive, Group) %>%summarise(Total =sum(Count, na.rm =TRUE), .groups ="drop")# plotting grouped bar chartg <-ggplot(nationality_summary, aes(x =reorder(Motive, -Total), y = Total, fill = Group)) +geom_bar(stat ="identity", position ="dodge") +# dodged barsscale_fill_manual(values =c("Internationals"="#933b94", # custom colors"Nationals"="#0D0887" )) +labs(title ="Total Nationals and International Impacted by Motive Type",subtitle ="Nationals vs Internationals (Grouped Bars)",x ="", y ="Total", fill ="Group") + my_theme +theme(axis.text.x =element_text(angle =45, hjust =1))ggplotly(g) # making it interactive
Heatmap
# aggregating wounding type by motive type and splitting btwn nats/internationalsharm_long <- events %>%filter(!is.na(Motive) & Motive !="") %>%# ignore missingpivot_longer( # pivoting all to long formatcols =c( Nationals.killed, Nationals.wounded, Nationals.kidnapped, Internationals.killed, Internationals.wounded, Internationals.kidnapped ), # renamesnames_to =c("Group", "HarmType"),names_sep ="\\.",values_to ="Count" ) %>%mutate(Group =ifelse(Group =="Nationals", "Nationals", "Internationals"),HarmType = stringr::str_to_title(HarmType) )# aggregating for the heatmapheatmap_data <- harm_long %>%# sums # of harmed for each motive, group, harm typegroup_by(Group, Motive, HarmType) %>%summarise(Total =sum(Count, na.rm =TRUE), .groups ="drop")# creating the heatmapg <-ggplot(heatmap_data, aes(x = HarmType, y = Motive, fill = Total)) +geom_tile(color ="white") +facet_wrap(~ Group) +# facet by national/internationalscale_fill_viridis_c(option ="plasma") +labs(title ="Type of Harm by Motive for Nationals and Internationals",subtitle ="Separated by Group (Nationals vs Internationals)",x =NULL, y =NULL, fill ="Total" ) + my_theme +# adding my themetheme(axis.text.x =element_text(angle =30, hjust =1),strip.text =element_text(size =12, face ="bold"),strip.background =element_blank(), ) ggplotly(g) # making it interactive